const { join, dirname, delimiter } = require("path");
const fs = require("fs");

const fluent = require("fluent-ffmpeg");

const { deleteDirectory, makeFolde, md5 } = require("./util");
const path = require("path");

const defaultConfig = {
  outputOptions: ["-hls_list_size 2", "-hls_flags delete_segments"],
  outputSuffix: "/list.m3u8",
  killDelaySecond: 10,
};

/**
 * 创建并运行ffmpeg转换rtsp流为hls进程
 *
 * @param {object} options - 函数接收的参数对象
 * @param {string} options.rtsp - rtsp流地址
 * @param {string[]} options.outputOptions - 输出选项，默认为 defaultConfig.outputOptions
 * @param {string} options.output - hls文件保存路径
 * @param {function} [options.onStart] - 开始处理时的回调函数
 * @param {function} [options.onError] - 出现错误时的回调函数
 * @returns {object} fluent 对象，用于 ffmpeg 命令的构建和执行
 */
function ffmpeg({
  rtsp,
  outputOptions,
  output,
  onStart = () => {},
  onError = () => {},
}) {
  const process = fluent();
  process
    .input(rtsp)
    .size("50%")
    // 输出选项
    .outputOptions(outputOptions)
    // hls文件保存路径
    .output(output)

    .on("start", onStart)
    .on("error", (err, stdout, stderr) => {
      onError(output, rtsp);
    })
    .run();

  return process;

  /*******************test**************/
  return {
    kill() {
      console.log("销毁进程：", rtsp);
    },
  };
}

/**
 * @class FfmpegManager
 * @description 创建并管理ffmpeg进程类
 */
// TODOS 监控断网rtsp失效时ffmpeg进程会崩溃，这时候应该删除ffmpegMap对应项
class FfmpegManager {
  ffmpegMap = new Map();
  stopTimerMap = new Map();

  /**
   * @constructor
   * @param {object} options - 构造函数参数
   * @param {string} options.cachePath - 静态资源路径，用于存放hls流缓存
   */
  constructor({ cachePath, ...configs }) {
    this.cachePath = cachePath;
    this.configs = configs;
  }

  /**
   * 创建ffmpeg进程，返回.m3u8文件url
   *
   * @param {string} rtsp - rtsp流地址
   * @returns {string} hls路径：localhost:3000/dfsf-a234-sd3f-sadf/list.m3u8
   */
  create(rtsp) {
    this.cancelStop(rtsp);
    if (this.ffmpegMap.has(rtsp)) return this.ffmpegMap.get(rtsp).url;

    const id = md5(rtsp);
    const { outputOptions, outputSuffix } = defaultConfig;
    // 创建hls缓存文件夹路径
    const hlsDir = join(this.cachePath, "/" + id);

    // 有文件夹则清空目录，没有则创建文件夹
    if (fs.existsSync(hlsDir)) {
      deleteDirectory(hlsDir);
    } else {
      makeFolde(hlsDir);
    }

    // const output = hlsDir + outputSuffix;
    const output = join(hlsDir, outputSuffix);

    const ffmpegProcess = ffmpeg({
      rtsp,
      outputOptions,
      output,
      ...this.configs,
    });
    const url = id + outputSuffix;
    this.ffmpegMap.set(rtsp, { url, ffmpeg: ffmpegProcess });
    return url;
  }

  /**
   * 默认10秒后结束所有ffmpeg进程，清空ffmpeg集合
   *
   * @param {number} delay - 秒
   */
  stopAll() {
    this.ffmpegMap.forEach((ffmpeg, rtsp) => {
      this.stopOne(rtsp);
    });
  }

  /**
   * 单独或批量根据rtsp地址杀死ffmpeg进程
   *
   * @param {string|string[]} rtsp - 字符串或者数组
   */
  stop(rtsp) {
    if (typeof rtsp === "string") {
      this.stopOne(rtsp);
    } else if (Array.isArray(rtsp)) {
      this.stopBatch(rtsp);
    }
  }

  stopOne(rtsp) {
    if (this.stopTimerMap.has(rtsp)) return;
    console.log("kill ffmpeg process after 2 second", rtsp);
    const timer = setTimeout(() => {
      // 删除该进程的缓存文件夹
      const rtspDirName = dirname(this.ffmpegMap.get(rtsp).url);
      const hlsCacheDirPath = join(this.cachePath, "/" + rtspDirName);
      deleteDirectory(hlsCacheDirPath, true);

      this.ffmpegMap.get(rtsp)?.ffmpeg.kill();
      this.ffmpegMap.delete(rtsp);
      this.stopTimerMap.delete(rtsp);

      console.log("killed ffmpeg process", rtsp);
    }, defaultConfig.killDelaySecond * 1000);

    this.stopTimerMap.set(rtsp, timer);
  }

  stopBatch(rtsps) {
    rtsps.forEach((rtsp) => this.stopOne(rtsp));
  }

  cancelStop(rtsp) {
    const timer = this.stopTimerMap.get(rtsp);
    clearTimeout(timer);
    this.stopTimerMap.delete(rtsp);
  }

  /**
   *
   * @returns {string[]}
   */
  getAllRtsp() {
    return Array.from(this.ffmpegMap.keys());
  }
}

module.exports = {
  FfmpegManager,
};
